/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Decorator, Node, SourceFile, SyntaxKind } from 'ts-morph';
import { AbstractAnnotationAnalyzer } from './interface/AbstractAnnotationAnalyzer';
import { IConstantResolver } from './interface/IConstantResolver';
import { DecoratorParser } from './utils/DecoratorParser';
import { AnnotationConstants, PluginConstants } from '../constants';
import { BaseAnalyzeResult, Logger } from '../../framework';
import { ErrorCode, PluginError } from '../error/PluginError';
import { HMRouterExtensionContext } from '../HMRouterExtensionContext';

/**
 * Router annotation analyzer
 *
 * This analyzer is responsible for parsing and extracting information from
 * @HMRouter annotations in source files. It identifies components with these
 * annotations and extracts router configuration parameters.
 *
 * It also performs validation to ensure proper usage of NavDestination components.
 */
export class RouterAnalyzer extends AbstractAnnotationAnalyzer {
  /**
   * Unique identifier for this analyzer
   */
  readonly name = 'RouterAnalyzer';

  /**
   * Constructor
   *
   * @param {IConstantResolver} constantResolver - Resolver for constants in annotations
   */
  constructor(constantResolver: IConstantResolver) {
    super(constantResolver);
  }

  /**
   * Analyze a source file for @HMRouter annotations
   *
   * This method scans the source file for @HMRouter annotations on components,
   * extracts configuration information, and adds the results to the context.
   * It also performs validation of NavDestination usage.
   *
   * @param {SourceFile} sourceFile - The source file to analyze
   * @param {string} filePath - Path to the source file
   * @param {HMRouterExtensionContext} context - Extension context for storing results
   */
  analyze(sourceFile: SourceFile, filePath: string, context: HMRouterExtensionContext): void {
    Logger.debug('', `Start to analyze source file: ${filePath}`);

    // Analyze @HMRouter annotations in MissingDeclaration nodes (struct declarations in ArkTS)
    sourceFile.getChildrenOfKind(SyntaxKind.MissingDeclaration).forEach((node) => {
      const componentName = this.extractComponentNameFromNode(node);

      node.getChildrenOfKind(SyntaxKind.Decorator).forEach((decorator) => {
        if (decorator.getName() === AnnotationConstants.ROUTER_ANNOTATION) {
          const result = this.addToResultSet(decorator, componentName, filePath, sourceFile);
          context.addAnalyzeResults(result);
        }
      });
    });

    // Analyze @HMRouter annotations in export assignments (for default exports)
    sourceFile.getExportAssignments().forEach((exportAssignment) => {
      const componentName = exportAssignment.getNextSiblingIfKind(SyntaxKind.ExpressionStatement)?.getText() || '';

      exportAssignment.getDescendantsOfKind(SyntaxKind.Decorator).forEach((decorator) => {
        if (decorator.getName() === AnnotationConstants.ROUTER_ANNOTATION) {
          const result = this.addToResultSet(decorator, componentName, filePath, sourceFile);
          context.addAnalyzeResults(result);

          // Mark this component as using default export for the code generator
          context.addTemplateData(result.name, { isDefaultExport: 'true' });
        }
      });
    });

    // Validate that components don't misuse NavDestination
    this.checkNavDestinationUsage(sourceFile, filePath);
  }

  /**
   * Create an analysis result from a decorator
   *
   * @param {Decorator} decorator - The @HMRouter decorator
   * @param {string} componentName - The name of the component
   * @param {string} filePath - Path to the source file
   * @param {SourceFile} sourceFile - The source file
   * @returns {BaseAnalyzeResult} The analyze result
   * @private
   */
  private addToResultSet(
    decorator: Decorator,
    componentName: string,
    filePath: string,
    sourceFile: SourceFile
  ): BaseAnalyzeResult {
    // Parse the decorator to extract configuration parameters
    const decoratorResult = DecoratorParser.parseDecorator(decorator, this.constantResolver, sourceFile, filePath);

    decoratorResult.name = componentName;
    decoratorResult.sourceFilePath = filePath;

    return decoratorResult;
  }

  /**
   * Check for proper NavDestination usage in components with @HMRouter
   *
   * This method validates that components with @HMRouter annotations
   * don't directly use NavDestination() unless useNavDst: true is specified.
   * Directly using NavDestination in a @HMRouter component causes conflicts
   * with the router's built-in NavDestination handling.
   *
   * @param {SourceFile} sourceFile - The source file to check
   * @param {string} filePath - Path to the source file
   * @throws {PluginError} If NavDestination is used incorrectly
   * @private
   */
  private checkNavDestinationUsage(sourceFile: SourceFile, filePath: string): void {
    // Sort statements to maintain the correct order of processing
    const statements = sourceFile.getStatements();
    const sortedStatements = statements.sort((a, b) => a.getStart() - b.getStart());

    let HMRouterExists: boolean = false; // Found a @HMRouter annotation
    let useNavDst: boolean = false; // Found useNavDst:true parameter

    sortedStatements.forEach((statement) => {
      // Check for @HMRouter annotation
      if (
        statement.getKind() === SyntaxKind.MissingDeclaration &&
        statement.getText().includes(AnnotationConstants.ROUTER_ANNOTATION)
      ) {
        // Reset flags for this component
        HMRouterExists = true;
        useNavDst = false;

        // Check if @HMRouter has useNavDst:true parameter
        const text = statement.getText();
        if (/(useNavDst\s*:\s*true)/.test(text)) {
          Logger.debug(
            '',
            `Found useNavDst: true in @HMRouter annotation, skip NavDestination check, filePath: ${filePath}`
          );
          useNavDst = true;
        }
      }

      // Process code block that follows an @HMRouter annotation
      if (statement.getKind() === SyntaxKind.Block && HMRouterExists) {
        HMRouterExists = false;

        // Skip check if useNavDst: true was specified
        if (useNavDst) {
          return;
        }

        // Check for direct NavDestination() usage
        const reg = new RegExp(/NavDestination\s*\(\s*\)/);
        const text = statement.getText();

        // Clean the code text to avoid false positives from strings and comments
        const cleanedCodeBlock = text
          .replace(/(["'`]).*?\1/g, '') // Remove string literals
          .replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''); // Remove comments

        // Throw error if NavDestination() is found
        if (reg.test(cleanedCodeBlock)) {
          throw PluginError.create(ErrorCode.WRONG_DECORATION, '', filePath);
        }
      }
    });
  }

  private extractComponentNameFromNode(node: Node): string {
    const nextSibling = node.getNextSiblingIfKind(SyntaxKind.ExpressionStatement);
    if (nextSibling && nextSibling.getText() === PluginConstants.STRUCT_KEYWORD) {
      const componentName = nextSibling.getNextSiblingIfKind(SyntaxKind.ExpressionStatement);
      if (componentName) {
        return componentName.getText();
      }
    }
    return '';
  }
}
